
多對多的關係可以看到 products、tags 及 product_tags 這 3 張表的關係,每個產品可以有多個標籤、每個標籤可以用在多種產品上,這就是多對多的設計,像這樣的關係就會需要第三方的關聯表來記錄,透過第三方表紀錄雙方的 PK 才能夠將關聯串起。
這邊預計將 Product 資料可以關聯出 Tag 所以 Product 為父實體
建立父實體 Products Entity
@Entity
@Getter
@Setter
@Table(name = "products")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private Double price;
    private String description;
    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name = "product_detail_id", referencedColumnName = "id")
    private ProductDetails productDetails;
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "product")
    private List<Review> reviews;
		// 多對多關連
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(
            name = "product_tags",
            joinColumns = @JoinColumn(name = "product_id"),
            inverseJoinColumns = @JoinColumn(name = "tag_id")
    )
    private Set<Tag> tags = new HashSet<>();
}
因為要關聯透過多對多關連到 Tag 來回傳一個集合,這種透過第三方表格來關聯就必須標示清楚和相關欄位之間的關係,所以要用到 @JoinTable 來關聯,內部有 4 個參數:
name = "product_tags" 就是表示第三方表的名稱joinColumns = @JoinColumn(name = "product_id") name 表示第三方表的第一個 FKinverseJoinColumns  = @JoinColumn(name = "tag_id") name 表示第第三方表的第二個FKuniqueConstraints = @UniqueConstraint(columnNames = {"product_id", "tag_id"}) 這個如果建立 table 有明確定義就可以不用寫,columnNames是表示  "product_id", "tag_id 兩個關聯 FK 綁一組作為唯一識別第三方表的 PK,避免插入相同組合的值。多對多關聯因 Lombok @Data 註解可能發生錯誤 :
這邊我在實際操作的時候,發現 @Data 註解會導致如果要回傳成 Set 的類型沒辦法成功,導致有些資料抓不到關聯,上網查了一些資料跟詢問 AI 後推測可能錯誤是因為 @Data 內包含的 @EqualsAndHashCode 覆寫 equals() 和 hashCode() 方法所導致。
要解決有查到幾種改法:
建立子實體 Tag Entity
@Entity
@Data
@Table(name = "tags")
@JsonIgnoreProperties({"products"})
public class Tag {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @ManyToMany(mappedBy = "tags")
    private Set<Product> products;
}
因為雙向關聯會導致雙向循環問題,所以這邊用到一個 @JsonIgnoreProperties({"products"}) 來避免 Tag 的資料又關聯回 Product ,如果這邊不需要 products 欄位其實可以直接都拿掉,也可以不用 ignore 註解
得到的 Product 回傳
[
    {
        "id": 1,
        "name": "最後生還者",
        "price": 59.99,
        "description": "由 Naughty Dog 開發的動作冒險遊戲。",
        "productDetails": {
            "id": 1,
            "developer": "Naughty Dog",
            "publisher": "Sony Interactive Entertainment",
            "releaseDate": "2013-06-14",
            "languageSupport": "English, Japanese, Chinese"
        },
        "reviews": [
            {
                "id": 1,
                "comment": "這是一款令人驚嘆的遊戲,擁有引人入勝的故事和令人驚嘆的視覺效果!",
                "rating": 5
            },
            {
                "id": 11,
                "comment": "遊戲不錯,但希望戰鬥系統能更具挑戰性。",
                "rating": 4
            },
            {
                "id": 12,
                "comment": "每一秒鐘都充滿樂趣,真的是一款精緻的遊戲!",
                "rating": 5
            }
        ],
        "tags": [
            {
                "id": 2,
                "name": "冒險"
            },
            {
                "id": 3,
                "name": "角色扮演"
            }
        ]
    },
    {
        "id": 2,
        "name": "巫師3",
        "price": 49.99,
        "description": "由 CD Projekt Red 開發的開放世界角色扮演遊戲。",
        "productDetails": {
            "id": 2,
            "developer": "CD Projekt Red",
            "publisher": "CD Projekt",
            "releaseDate": "2015-05-19",
            "languageSupport": "English, Chinese, Polish"
        },
        "reviews": [
            {
                "id": 2,
                "comment": "很棒的遊戲,但某些部分的節奏感覺有點慢。",
                "rating": 4
            },
            {
                "id": 13,
                "comment": "畫質很好,但劇情有點單調。",
                "rating": 3
            }
        ],
        "tags": [
            {
                "id": 7,
                "name": "奇幻"
            },
            {
                "id": 3,
                "name": "角色扮演"
            }
        ]
    },
    //......略
]
關於 Spring Data Jpa 的關聯就介紹到這邊,詳細很多實作上的問題都是自己做過才會碰到,查詢這些問題也是很重要,實際解決也更能理解底層原因跟避免下次又發生,下一篇會來介紹一下 Lombok 的註解用法,也剛好對應到這篇的一些問題上面。
Ref:
相關文章也會同步更新我的部落格,有興趣也可以在裡面找其他的技術分享跟資訊。